/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.controller.admin;

import cn.easyplatform.lang.Lang;
import cn.easyplatform.messages.request.SimpleRequestMessage;
import cn.easyplatform.messages.vos.admin.HomeVo;
import cn.easyplatform.spi.service.AdminService;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.web.Version;
import cn.easyplatform.web.dialog.MessageBox;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.utils.ExtUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.zkoss.chart.*;
import org.zkoss.chart.plotOptions.GaugePlotOptions;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Label;
import org.zkoss.zul.Tabpanel;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Author: <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @Description:
 * @Since: 2.0.0 <br/>
 * @Date: Created in 2019/11/26 17:41
 * @Modified By:
 */
public class HomeController extends SelectorComposer<Component> {

    @Wire("label#userCount")
    private Label userCount;

    @Wire("label#userRate")
    private Label userRate;

    @Wire("label#dbCount")
    private Label dbCount;

    @Wire("label#dbRate")
    private Label dbRate;

    @Wire("label#diskCount")
    private Label diskCount;

    @Wire("label#diskRate")
    private Label diskRate;

    @Wire("label#coreCount")
    private Label coreCount;

    @Wire("label#coreRate")
    private Label coreRate;

    @Wire("label#epVersion")
    private Label epVersion;

    @Wire("label#vmVersion")
    private Label vmVersion;

    @Wire("label#vmVendor")
    private Label vmVendor;

    @Wire("label#vmArgs")
    private Label vmArgs;

    @Wire("label#osName")
    private Label osName;

    @Wire("label#osArch")
    private Label osArch;

    @Wire("label#processors")
    private Label processors;

    @Wire("label#pid")
    private Label pid;

    @Wire("label#totalMemory")
    private Label totalMemory;

    @Wire("label#startTime")
    private Label startTime;

    @Wire("label#upTime")
    private Label upTime;

    @Wire("label#vmCommand")
    private Label vmCommand;

    @Wire("label#totalThread")
    private Label totalThread;

    @Wire("charts#gaugeChart")
    private Charts gaugeChart;

    @Wire("charts#cpuChart")
    private Charts cpuChart;

    @Wire("charts#memoryChart")
    private Charts memoryChart;

    private long maxMemoryUsed;

    private double maxCpuUsed;

    private com.sun.management.OperatingSystemMXBean osm;

    private MemoryMXBean mmb;

    private RuntimeMXBean rmb;

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        AdminService as = ServiceLocator.lookup(AdminService.class);
        IResponseMessage<?> resp = as.getHome(new SimpleRequestMessage());
        if (resp.isSuccess()) {
            HomeVo hv = (HomeVo) resp.getBody();
            userCount.setValue(hv.getUsers() + "");
            dbCount.setValue(hv.getPools()[0] + "");
            dbRate.setValue(format(hv.getPools()[0], hv.getPools()[1]));
            File file = null;
            try {
                file = File.createTempFile(RandomStringUtils.randomAlphanumeric(10), "tmp");
                long used = file.getTotalSpace() - file.getUsableSpace();
                diskCount.setValue(format(used));
                diskRate.setValue(format(used, file.getTotalSpace()));
            } catch (Exception e) {
            } finally {
                FileUtils.deleteQuietly(file);
            }
            if (hv.getCores()[1] > 0) {
                coreCount.setValue(String.format("%d", hv.getCores()[0]));
                coreRate.setValue(format(hv.getCores()[0], hv.getCores()[1]));
            } else {
                coreCount.setValue("N/A");
                coreRate.setValue("N/A");
            }

            rmb = ManagementFactory.getRuntimeMXBean();
            osm = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
            mmb = ManagementFactory.getMemoryMXBean();

            epVersion.setValue("V" + Version.VERSION + "  (" + Version.TIMESTAMP + ")");
            vmVersion.setValue(rmb.getVmName() + " " + rmb.getSpecVersion() + "-" + rmb.getVmVersion());
            vmVersion.setTooltiptext(vmVersion.getValue());
            vmVendor.setValue(rmb.getVmVendor());
            vmVendor.setTooltiptext(vmVendor.getValue());
            vmArgs.setValue(StringUtils.substringBetween(Arrays.toString(rmb.getInputArguments().toArray()), "[", "]"));
            vmArgs.setTooltiptext(vmArgs.getValue());

            osName.setValue(osm.getName());
            osName.setTooltiptext(osName.getValue());
            osArch.setValue(osm.getArch());
            osArch.setTooltiptext(osArch.getValue());
            processors.setValue(osm.getAvailableProcessors() + Labels.getLabel("admin.jvm.code"));
            pid.setValue(rmb.getName().split("@")[0]);
            totalMemory.setValue(String.format("%.2fG", osm.getTotalPhysicalMemorySize() / (Math.pow(1024.0, 3.0))));
            startTime.setValue(DateFormatUtils.format(rmb.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
            upTime.setValue(ExtUtils.calculateInterval(rmb.getUptime()));
            vmCommand.setValue(System.getProperty("sun.java.command"));
            vmCommand.setTooltiptext(vmCommand.getValue());
            totalThread.setValue(ManagementFactory.getThreadMXBean().getThreadCount() + "");

            //LinearGradient linearGradient = new LinearGradient(0, 0, 0, 1);
            //linearGradient.addStop(0, "transparent");
            //linearGradient.addStop(0.3, "transparent");
            //linearGradient.addStop(1, "transparent");
            //gaugeChart.setPlotBackgroundColor(linearGradient);
            gaugeChart.getExporting().setEnabled(false);
            Pane pane1 = gaugeChart.getPane();
            pane1.setStartAngle(-55);
            pane1.setEndAngle(55);
            pane1.setBackground(new ArrayList<PaneBackground>());
            pane1.setCenter("25%", "145%");
            pane1.setSize(300);

            Pane pane2 = gaugeChart.getPane(1);
            pane2.setStartAngle(-55);
            pane2.setEndAngle(55);
            pane2.setBackground(new ArrayList<PaneBackground>());

            pane2.setCenter("75%", "145%");
            pane2.setSize(300);

            long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
            if (heapSize > 999) {
                heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
            } else {
                heapSize = 1024;
            }
            YAxis yAxis1 = gaugeChart.getYAxis();
            yAxis1.setMin(0);
            yAxis1.setMax(heapSize);
            yAxis1.setMinorTickPosition("outside");
            yAxis1.setTickPosition("outside");
            List<Number> positions = new ArrayList<>();
            int increment = new BigDecimal(heapSize / 10).intValue();
            if (heapSize > 999)
                increment = increment - (increment % 100);
            else
                increment = increment - (increment % 50);
            for (double tick = 0; tick <= heapSize; tick += increment)
                positions.add(tick);
            yAxis1.setTickPositions(positions);
            yAxis1.getLabels().setRotation("auto");
            yAxis1.getLabels().setDistance(20);

            PlotBand plotBand = new PlotBand();
            plotBand.setFrom(0);
            plotBand.setTo(0.6 * heapSize);
            plotBand.setColor("#55BF3B");//绿
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis1.addPlotBand(plotBand);

            plotBand = new PlotBand();
            plotBand.setFrom(0.6 * heapSize);
            plotBand.setTo(0.8 * heapSize);
            plotBand.setColor("#DDDF0D");//黄
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis1.addPlotBand(plotBand);

            plotBand = new PlotBand();
            plotBand.setFrom(0.8 * heapSize);
            plotBand.setTo(heapSize);
            plotBand.setColor("#C02316");//红
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis1.addPlotBand(plotBand);

            maxMemoryUsed = mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024;
            maxCpuUsed = osm.getProcessCpuLoad() * 100;
            yAxis1.setPane(0);
            yAxis1.setTitle("<strong>" + Labels.getLabel("admin.jvm.memory") + "(M)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + maxMemoryUsed + "M  " + Labels.getLabel("admin.jvm.max") + ":" + maxMemoryUsed + "M");
            yAxis1.getTitle().setY(-40);

            YAxis yAxis2 = gaugeChart.getYAxis(1);
            yAxis2.setMin(0);
            yAxis2.setMax(100);
            yAxis2.setMinorTickPosition("outside");
            yAxis2.setTickPosition("outside");
            yAxis2.getLabels().setRotation("auto");
            List<Number> ps = new ArrayList<>();
            for (int i = 0; i <= 10; i++)
                ps.add(i * 10);
            yAxis2.setTickPositions(ps);
            yAxis2.getLabels().setDistance(20);

            plotBand = new PlotBand();
            plotBand.setFrom(0);
            plotBand.setTo(60);
            plotBand.setColor("#55BF3B");//绿
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis2.addPlotBand(plotBand);

            plotBand = new PlotBand();
            plotBand.setFrom(60);
            plotBand.setTo(80);
            plotBand.setColor("#DDDF0D");//黄
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis2.addPlotBand(plotBand);

            plotBand = new PlotBand();
            plotBand.setFrom(80);
            plotBand.setTo(100);
            plotBand.setColor("#C02316");//红
            plotBand.setInnerRadius("100%");
            plotBand.setOuterRadius("105%");
            yAxis2.addPlotBand(plotBand);

            yAxis2.setPane(1);
            yAxis2.setTitle("<strong>CPU(%)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + String.format("%.2f", maxCpuUsed) + "%  " + Labels.getLabel("admin.jvm.max") + ":" + String.format("%.2f", maxCpuUsed) + "%");
            yAxis2.getTitle().setY(-40);

            GaugePlotOptions plotOptions = gaugeChart.getPlotOptions().getGauge();
            plotOptions.getDataLabels().setEnabled(false);
            plotOptions.getDial().setRadius("100%");

            Series series1 = gaugeChart.getSeries();
            series1.setData(0);
            series1.setYAxis(0);

            Series series2 = gaugeChart.getSeries(1);
            series2.setData(0);
            series2.setYAxis(1);

            //cpu使用率
            Options options = new Options();
            options.getGlobal().setUseUTC(false);
            cpuChart.setOptions(options);
            cpuChart.setAnimation(true);

            cpuChart.getXAxis().setType("datetime");
            cpuChart.getXAxis().setTickPixelInterval(150);
            cpuChart.getYAxis().setMin(0);
            cpuChart.getYAxis().setMax(100);
            cpuChart.getYAxis().setTitle("");
            cpuChart.getYAxis().setTickInterval(10);
            cpuChart.getYAxis().getLabels().setFormat("{value}%");
            PlotLine plotLine = new PlotLine();
            plotLine.setValue(0);
            plotLine.setWidth(1);
            plotLine.setColor("#808080");
            cpuChart.getYAxis().addPlotLine(plotLine);
            cpuChart.getTooltip().setEnabled(false);

            cpuChart.getExporting().setEnabled(false);
            Legend legend = cpuChart.getLegend();
            legend.setLayout("vertical");
            legend.setAlign("right");
            legend.setVerticalAlign("top");
            legend.setY(0);
            legend.setFloating(true);

            Series jvmSeries = cpuChart.getSeries();
            jvmSeries.getMarker().setEnabled(false);
            jvmSeries.setName("JVM CPU Usage");
            jvmSeries.setColor("#32CD32");
            Series systemSeries = new Series();
            systemSeries.getMarker().setEnabled(false);
            systemSeries.setName("Machine CPU Usage");
            systemSeries.setColor("#436EEE");
            cpuChart.addSeries(systemSeries);
            for (int i = -19; i < 0; i++) {
                jvmSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
                systemSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            }
            jvmSeries.addPoint(new Point(System.currentTimeMillis(), maxCpuUsed));
            systemSeries.addPoint(new Point(System.currentTimeMillis(), osm.getSystemCpuLoad() * 100));

            //memory使用率
            memoryChart.setAnimation(true);

            memoryChart.getXAxis().setType("datetime");
            memoryChart.getXAxis().setTickPixelInterval(150);
            memoryChart.getYAxis().setMin(0);
            memoryChart.getYAxis().setMax(heapSize);
            memoryChart.getYAxis().setTitle("");
            memoryChart.getYAxis().setTickInterval(increment);
            memoryChart.getYAxis().getLabels().setFormat("{value}M");
            //memoryChart.getYAxis().getLabels().setFormatter(new JavaScriptValue(""));
            memoryChart.getTooltip().setEnabled(false);

            memoryChart.getExporting().setEnabled(false);
            legend = memoryChart.getLegend();
            legend.setLayout("vertical");
            legend.setAlign("right");
            legend.setVerticalAlign("top");
            legend.setY(0);
            legend.setFloating(true);

            Series committedSeries = memoryChart.getSeries();
            committedSeries.getMarker().setEnabled(false);
            committedSeries.setName("Committed Java Heap");
            Series maxSeries = new Series();
            maxSeries.getMarker().setEnabled(false);
            maxSeries.setName("Maximum Java Heap");
            memoryChart.addSeries(maxSeries);
            Series usedJavaSeries = new Series();
            usedJavaSeries.getMarker().setEnabled(false);
            usedJavaSeries.setName("Used Java Heap Memory");
            memoryChart.addSeries(usedJavaSeries);
            Series usedPhysicalSeries = new Series();
            usedPhysicalSeries.getMarker().setEnabled(false);
            usedPhysicalSeries.setName("Used Physical Memory");
            memoryChart.addSeries(usedPhysicalSeries);
            for (int i = -19; i < 0; i++) {
                committedSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
                maxSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
                usedJavaSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
                usedPhysicalSeries.addPoint(new Point(System.currentTimeMillis() + i * 1000, 0));
            }
            committedSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getCommitted() / 1024 / 1024));
            maxSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getMax() / 1024 / 1024));
            usedJavaSeries.addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024));
            usedPhysicalSeries.addPoint(new Point(System.currentTimeMillis(), (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024));

            committedSeries.hide();
            maxSeries.hide();
            usedPhysicalSeries.hide();

            final AtomicBoolean stop = new AtomicBoolean(false);
            new SchedulerThread(comp.getDesktop(), stop).start();
            final EventListener el = event -> {
                stop.set(true);
            };
            Tabpanel c = (Tabpanel) comp.getParent().getParent();
            c.getLinkedTab().addEventListener(Events.ON_CLOSE, el);
        } else {
            MessageBox.showMessage(resp);
            comp.detach();
        }
    }

    @Listen("onPlotHide = #memoryChart")
    public void hideSeries(ChartsEvent event) {
        long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
        if (heapSize > 999) {
            heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
        } else {
            heapSize = 1024;
        }
        event.getSeries().setVisible(false);
        if (!memoryChart.getSeries(3).isVisible()) {
            memoryChart.getYAxis().setMax(heapSize);
            int increment = new BigDecimal(heapSize / 10).intValue();
            if (heapSize > 999)
                increment = increment - (increment % 100);
            else
                increment = increment - (increment % 50);
            memoryChart.getYAxis().setTickInterval(increment);
        }
    }

    @Listen("onPlotShow = #memoryChart")
    public void showSeries(ChartsEvent event) {
        long heapSize = mmb.getHeapMemoryUsage().getMax() / 1024 / 1024;
        if (heapSize > 999) {
            heapSize = new BigDecimal((Math.floor(heapSize / 100) + 1) * 100).longValue();
        } else {
            heapSize = 1024;
        }
        event.getSeries().setVisible(true);
        if (event.getSeriesIndex() == 3) {//Used Physical Memory
            long usedPhysical = (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024;
            if (heapSize < usedPhysical) {
                memoryChart.getYAxis().setMax(usedPhysical);
                int increment = new BigDecimal(usedPhysical / 10).intValue();
                if (usedPhysical > 999)
                    increment = increment - (increment % 100);
                else
                    increment = increment - (increment % 50);
                memoryChart.getYAxis().setTickInterval(increment);
            }
        } else if (!memoryChart.getSeries(3).isVisible()) {
            memoryChart.getYAxis().setMax(heapSize);
            int increment = new BigDecimal(heapSize / 10).intValue();
            if (heapSize > 999)
                increment = increment - (increment % 100);
            else
                increment = increment - (increment % 50);
            memoryChart.getYAxis().setTickInterval(increment);
        }
    }

    private void updateData() {
        long memoryUsed = mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024;
        if (memoryUsed > maxMemoryUsed)
            maxMemoryUsed = memoryUsed;
        Point left = gaugeChart.getSeries().getPoint(0);
        left.update(memoryUsed);

        double cpuUsed = osm.getProcessCpuLoad() * 100;
        if (cpuUsed > maxCpuUsed)
            maxCpuUsed = cpuUsed;
        Point right = gaugeChart.getSeries(1).getPoint(0);
        right.update(cpuUsed);

        YAxis yAxis1 = gaugeChart.getYAxis();
        yAxis1.setTitle("<strong>" + Labels.getLabel("admin.jvm.memory") + "(M)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + memoryUsed + "M  " + Labels.getLabel("admin.jvm.max") + ":" + maxMemoryUsed + "M");
        YAxis yAxis2 = gaugeChart.getYAxis(1);
        yAxis2.setTitle("<strong>CPU(%)</strong><br/>" + Labels.getLabel("admin.jvm.now") + ":" + String.format("%.2f", cpuUsed) + "%  " + Labels.getLabel("admin.jvm.max") + ":" + String.format("%.2f", maxCpuUsed) + "%");

        cpuChart.getSeries().addPoint(new Point(System.currentTimeMillis(), cpuUsed), true, true, true);
        cpuChart.getSeries(1).addPoint(new Point(System.currentTimeMillis(), osm.getSystemCpuLoad() * 100), true, true, true);

        memoryChart.getSeries(0).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getCommitted() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(1).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getMax() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(2).addPoint(new Point(System.currentTimeMillis(), mmb.getHeapMemoryUsage().getUsed() / 1024 / 1024), true, true, true);
        memoryChart.getSeries(3).addPoint(new Point(System.currentTimeMillis(), (osm.getTotalPhysicalMemorySize() - osm.getFreePhysicalMemorySize()) / 1024 / 1024), true, true, true);

        totalThread.setValue(ManagementFactory.getThreadMXBean().getThreadCount() + "");
        upTime.setValue(ExtUtils.calculateInterval(rmb.getUptime()));
    }


    private String format(Object o1, Object o2) {
        BigDecimal b1 = new BigDecimal(o1.toString());
        BigDecimal b2 = new BigDecimal(o2.toString());
        return String.format("%.2f%%", ((b1.divide(b2, 4, BigDecimal.ROUND_HALF_UP)).doubleValue()) * 100);
    }

    private String format(long size) {
        if (size < 1024) {
            return size + "B";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            return size + "KB";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            size = size * 100;
            return (size / 100) + "."
                    + (size % 100) + "MB";
        } else {
            size = size * 100 / 1024;
            return (size / 100) + "."
                    + (size % 100) + "GB";
        }
    }

    private class SchedulerThread extends Thread {

        private Desktop desktop;

        private AtomicBoolean stop;

        SchedulerThread(Desktop desktop, AtomicBoolean stop) {
            this.desktop = desktop;
            this.stop = stop;
            if (!desktop.isServerPushEnabled())
                desktop.enableServerPush(true);
        }

        @Override
        public void run() {
            while (!stop.get()) {
                if (!desktop.isAlive())
                    return;
                Executions.schedule(desktop, event -> updateData(), new Event("onValue", null, null));
                Lang.sleep(1000);
            }
        }
    }
}
